﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Newtonsoft.Json.Linq;
using OpenTap.Plugins.Interfaces.DIO;
using OpenTap.Plugins.Interfaces.LedAnalyzer;

namespace OpenTap.Plugins.LedAnalyzer
{
    /// <summary>
    /// Hardware is an array of boards with Hamamatsu S9706 color sensors.
    /// </summary>
    [Display("DioLedAnalyzer", Group: "LED Analyzer", Description: "DIO LED Analyzer")]
    public class DioLedAnalyzer : Instrument, ILedAnalyzer
    {
        private const int RangeGateDelayMs = 2; // symbol t4 in S9706 data sheet, minimum 2000 us
        private const int DefaultGateIntegrationTimeMs = 150;
        private const int GateReadoutDelayMs = 1; // symbol t1 in S9706 data sheet, minimum 4 us
        private const int ReadoutClockHighMs = 1; // symbol tw in S9706 data sheet, minimum 200 ns
        private const int ReadoutClockLowMs = 1; // symbol tck-tw in S9706 data sheet, minimum 200...300 ns
        private const double InputThresholdLowV = 0.8;
        private const double InputThresholdHighV = 2.0;

        private int _gateIntegrationTimeMs = DefaultGateIntegrationTimeMs;
        private List<Rgb12BitData> _rgb12BitList;

        public enum EOutputMode
        {
            OpenCollector,
            ActiveDrive
        }

        #region Settings

        [Display("DIO Instrument",
            Order: 1.1)]
        public IDio Dio { get; set; }

        [Display("DIO Output Channels Mode",
            Order: 1.2,
            Description:
            "In 'OpenCollector' mode there are external 3.3 V pull-ups for S9706 sensors' pins.\n" +
            "In 'ActiveDrive' mode DIO output voltage is connected directly to S9706 sensors' pins. DIO output voltage must be 3.3 V!")]
        public EOutputMode OutputChannelsMode { get; set; } = EOutputMode.OpenCollector;

        [Display("DIO Output Channel for GATE Pins",
            Order: 2.1,
            Description: "DIO output channel that is connected to all S9706 sensors' GATE pins.")]
        public short GatePinChannel { get; set; }

        [Display("DIO Output Channel for CLK Pins",
            Order: 2.2,
            Description: "DIO output channel that is connected to all S9706 sensors' CLK pins.")]
        public short ClkPinChannel { get; set; }

        [Display("DIO Output Channel for RANGE Pins",
            Order: 2.3,
            Description: "DIO output channel that is connected to all S9706 sensors' RANGE pins.")]
        public short RangePinChannel { get; set; }

        [Display("DIO Input Channels for DOUT Pins",
            Order: 3,
            Description: "List of DIO output channels that are connected to individual S9706 sensor's DOUT pins.")]
        public List<short> DoutPinChannels { get; set; }

        #endregion

        public DioLedAnalyzer()
        {
            Name = "DioLedAnalyzer";
        }

        public void Initialize()
        {
            Log.Info("DioLedAnalyzer - Initialize");
        }

        public void CaptureData(EBrightnessLevel brightnessLevel)
        {
            var rgb12BitDataArray = new Rgb12BitData[DoutPinChannels.Count];

            /*
             * Set initial pin states.
             */
            Dio.SetOutputState(
                new List<short> { GatePinChannel, ClkPinChannel },
                new List<EOutputState>() { EOutputState.Sink, EOutputState.Sink });

            /*
             * Set input logic threshold levels.
             */
            Dio.SetThresholdLevel(new List<double>() { InputThresholdLowV, InputThresholdHighV });

            /*
             * Set range (= sensitivity).
             */
            if (brightnessLevel == EBrightnessLevel.Low)
                Dio.SetOutputState(new List<short> { RangePinChannel }, new List<EOutputState>() { EOutputState.Sink });
            else
                Dio.SetOutputState(new List<short> { RangePinChannel }, new List<EOutputState>() { GetOutputStateHigh() });
            Thread.Sleep(RangeGateDelayMs);

            /*
             * Integrate light.
             */
            Dio.SetOutputState(new List<short> { GatePinChannel }, new List<EOutputState>() { GetOutputStateHigh() });
            Thread.Sleep(_gateIntegrationTimeMs);
            Dio.SetOutputState(new List<short> { GatePinChannel }, new List<EOutputState>() { EOutputState.Sink });
            Thread.Sleep(GateReadoutDelayMs);

            /*
             * Set DOUT pin channel numbers to private array of 12-bit RGBs.
             */
            for (var s = 0; s < DoutPinChannels.Count; s++)
            {
                rgb12BitDataArray[s].Channel = DoutPinChannels.ElementAt(s);
            }

            /*
             * Read each color using 12 pulses to CLK.
             */
            // Red
            for (var i = 0; i < 12; i++)
            {
                Dio.SetOutputState(new List<short>() { ClkPinChannel }, new List<EOutputState>() { GetOutputStateHigh() });
                Thread.Sleep(ReadoutClockHighMs);
                Dio.SetOutputState(new List<short>() { ClkPinChannel }, new List<EOutputState>() { EOutputState.Sink });
                Thread.Sleep(ReadoutClockLowMs);

                var inputStates = Dio.GetInputState(DoutPinChannels);
                for (var s = 0; s < DoutPinChannels.Count; s++)
                {
                    if (inputStates.ElementAt(s) == EInputState.High)
                    {
                        rgb12BitDataArray[s].Red |= (ushort) (1 << i);
                    }
                }
            }
            // Green
            for (var i = 0; i < 12; i++)
            {
                Dio.SetOutputState(new List<short>() { ClkPinChannel }, new List<EOutputState>() { GetOutputStateHigh() });
                Thread.Sleep(ReadoutClockHighMs);
                Dio.SetOutputState(new List<short>() { ClkPinChannel }, new List<EOutputState>() { EOutputState.Sink });
                Thread.Sleep(ReadoutClockLowMs);

                var inputStates = Dio.GetInputState(DoutPinChannels);
                for (var s = 0; s < DoutPinChannels.Count; s++)
                {
                    if (inputStates.ElementAt(s) == EInputState.High)
                    {
                        rgb12BitDataArray[s].Green |= (ushort) (1 << i);
                    }
                }
            }
            // Blue
            for (var i = 0; i < 12; i++)
            {
                Dio.SetOutputState(new List<short>() { ClkPinChannel }, new List<EOutputState>() { GetOutputStateHigh() });
                Thread.Sleep(ReadoutClockHighMs);
                Dio.SetOutputState(new List<short>() { ClkPinChannel }, new List<EOutputState>() { EOutputState.Sink });
                Thread.Sleep(ReadoutClockLowMs);

                var inputStates = Dio.GetInputState(DoutPinChannels);
                for (var s = 0; s < DoutPinChannels.Count; s++)
                {
                    if (inputStates.ElementAt(s) == EInputState.High)
                    {
                        rgb12BitDataArray[s].Blue |= (ushort) (1 << i);
                    }
                }
            }

            _rgb12BitList = rgb12BitDataArray.ToList();
        }

        public void CaptureDataPwm(EBrightnessLevel brightnessLevel, int averageFactor = 0)
        {
            throw new NotImplementedException();
        }

        public RgbData GetRgb(int channel)
        {
            Log.Info("DioLedAnalyzer - GetRgb");
            if (_rgb12BitList.Exists(x => x.Channel == channel))
            {
                var rgb12Bit = _rgb12BitList.Find(x => x.Channel == channel);
                return Rgb12To8Bit(rgb12Bit);
            }
            return new RgbData();
        }

        public List<RgbData> GetRgbs()
        {
            Log.Info("DioLedAnalyzer - GetRgbs");
            return _rgb12BitList.Select(Rgb12To8Bit).ToList();
        }

        public HsiData GetHsi(int channel)
        {
            throw new NotImplementedException();
        }

        public List<HsiData> GetHsiGroup(int channelgroup)
        {
            throw new NotImplementedException();
        }

        public ChromaticityData GetChromaticity(int channel)
        {
            throw new NotImplementedException();
        }

        public void Initialize(JObject configElement)
        {
            throw new NotImplementedException();
        }

        public void SetGateIntegrationTimeMs(int timeMs)
        {
            _gateIntegrationTimeMs = timeMs;
        }

        private struct Rgb12BitData
        {
            public int Channel;
            public ushort Red;
            public ushort Green;
            public ushort Blue;
        }

        private RgbData Rgb12To8Bit(Rgb12BitData rgb12Bit)
        {
            var rgb8Bit = new RgbData
            {
                Red = (byte)(rgb12Bit.Red >> 4),
                Green = (byte)(rgb12Bit.Green >> 4),
                Blue = (byte)(rgb12Bit.Blue >> 4)
            };
            // Convert 12-bit values to 8-bit values.
            Log.Debug("RGB 12-bit: " + rgb12Bit.Red + ", " + rgb12Bit.Green + ", " + rgb12Bit.Blue);
            Log.Info("RGB 8-bit: " + rgb8Bit.Red + ", " + rgb8Bit.Green + ", " + rgb8Bit.Blue);
            return rgb8Bit;
        }

        private EOutputState GetOutputStateHigh()
        {
            var retVal = OutputChannelsMode == EOutputMode.ActiveDrive ? EOutputState.Source : EOutputState.Off;
            return retVal;
        }
    }
}
